package IC.lir;

import java.util.Arrays;
import java.util.LinkedList;


import IC.AST.*;
import IC.lir.Instrunction.*;
import IC.lir.Instrunction.JumpInstruction.Condition;


public class OptimizedFunctionBodyCreatorVisitor extends FunctionBodyCreatorVisitor {
	
	public OptimizedFunctionBodyCreatorVisitor(Lir lirStruct) { super(lirStruct); }

	public Object Visit(Program rootNode)
	{
		new RegisterWeightCalcVisitor().Visit(rootNode);
		return super.Visit(rootNode);
	}
	
	public Object Visit(AssignStatement node)
	{
		_registerCounter = 0;
		
		Memory where;
		Storable what;
		
		if (node.GetWhereTo().GetNumOfRegisters() >= node.GetWhat().GetNumOfRegisters())
		{
			_returnAsWhat = ReturnAsWhat.Memory;
			where = (Memory)(node.GetWhereTo().Visit(this));
			_returnAsWhat = ReturnAsWhat.Register;
			
			_registerCounter = where.GetNumOfRegisters();
			what = (Storable)InsureNotNegativeImmediate((Data)(node.GetWhat().Visit(this)));
		}
		else
		{
			what = (Storable)InsureNotNegativeImmediate((Data)(node.GetWhat().Visit(this)));
			
			_registerCounter = 1;
			
			_returnAsWhat = ReturnAsWhat.Memory;
			where = (Memory)(node.GetWhereTo().Visit(this));
			_returnAsWhat = ReturnAsWhat.Register;
		}
		
		_currentFunctionInstructions.add(where.Store(what));		
		return null; 
	}
	
	protected Storable GenerateCompareCodeImplementaion(BinaryOperationExpression node, Condition jumpTrueCond)
	{
		if (node.IsExpValueSet())
			return new Immediate(node.GetExpValue());
		
		int registerCounter = _registerCounter;
		
		boolean evaluateFirstExpFirst = 
			node.GetFirstExp().GetNumOfRegisters() >= node.GetSecondExp().GetNumOfRegisters();
		
		ReturnAsWhat returnAsWhat = _returnAsWhat;
		
		_returnAsWhat = ReturnAsWhat.Register;
		if (evaluateFirstExpFirst)
			node.GetFirstExp().Visit(this);
		else
			node.GetSecondExp().Visit(this);
		
		_registerCounter = registerCounter + 1;
		
		_returnAsWhat = ReturnAsWhat.SimpleMemory;
		Data secondOperand = InsureNotNegativeImmediate(
				evaluateFirstExpFirst ? 
						(Data)(node.GetSecondExp().Visit(this))
					:
						(Data)(node.GetFirstExp().Visit(this)));
		_returnAsWhat = returnAsWhat;
		
		Register retRegister = new Register(_registerCounter = registerCounter);
		
		InsertCompareCodeLogic(retRegister, 
				secondOperand, 
				evaluateFirstExpFirst ? jumpTrueCond : jumpTrueCond.InvertSides());
		
		_registerCounter = registerCounter + 1;
		return retRegister;
	}
	
	protected Data GenerateStringCatCode(Expression first, Expression second, ASTNode node)
	{
		if (node.IsExpValueSet())
			return (Data)(new QuoteLiteral(node.GetInterval(), node.GetExpStringValue()).Visit(this));
		
		ReturnAsWhat returnAsWhat = _returnAsWhat;
		
		boolean wasThereSwap = !(first.GetNumOfRegisters() >= second.GetNumOfRegisters());
		if (wasThereSwap)
		{
			Expression swap = first;
			first = second;
			second = swap;
		}
			
		int registerCounter = _registerCounter;
		
		_returnAsWhat = ReturnAsWhat.SimpleMemory;
		Data firstParam = (Data)(first.Visit(this));
		_registerCounter = registerCounter + (firstParam instanceof Register ? 1 : 0);
		
		_returnAsWhat = ReturnAsWhat.SimpleMemory;
		Data secondParam = (Data)(second.Visit(this));
		Register retRegister = new Register(registerCounter);
		_registerCounter = registerCounter + 1;

		_currentFunctionInstructions.add(
				new StringCatInstruction(retRegister, 
						wasThereSwap ? secondParam : firstParam, 
						wasThereSwap ? firstParam : secondParam));

		_returnAsWhat = returnAsWhat;
		return retRegister;
	}
	
	protected Storable GenerateArithmeticBinaryCode(BinaryOperationExpression.Operation op, Expression first, Expression second, ASTNode node)
	{
		if (node.IsExpValueSet())
			return new Immediate(node.GetExpValue());
		
		Register retRegister = new Register(_registerCounter);
		
		Data secondOperand;
		if (first.GetNumOfRegisters() >= second.GetNumOfRegisters())
		{
			MakeSureDataIsRegister(InsureNotNegativeImmediate((Data)first.Visit(this)));
			_registerCounter = retRegister.GetIndex() + 1;

			secondOperand = InsureNotNegativeImmediate((Data)(second.Visit(this)));
			_registerCounter = retRegister.GetIndex() + 1;
		}
		else
		{
			ReturnAsWhat returnAsWhat = _returnAsWhat;
			
			_returnAsWhat = ReturnAsWhat.Register;
			MakeSureDataIsRegister(InsureNotNegativeImmediate((Data)(second.Visit(this))));
			_registerCounter = retRegister.GetIndex() + 1;
			_returnAsWhat = returnAsWhat;

			secondOperand = InsureNotNegativeImmediate((Data)first.Visit(this));
			_registerCounter = retRegister.GetIndex() + 1;
			
			if (secondOperand instanceof Register && !op.Isflippable())
				GenerateRegistersSwap(
						new Register(_registerCounter - 1), new Register(_registerCounter), new Register(_registerCounter + 1));
		}
		
		_currentFunctionInstructions.add(
				new BinaryArithmeticInstruction(
						ConvertOperationType(op),
						secondOperand,
						retRegister));
		
		_registerCounter = retRegister.GetIndex() + 1;
		return retRegister;
	}
	
	
	public Object Visit(VirtualFunctionCall node)
	{
		node.GetParams().addFirst(node.GetInvokedExpression());
		
		LinkedList<Data> Params = ConvertExpressionListToParams(node.GetParams(), true);
		Register objRegister = (Register)(Params.removeFirst());
		node.GetParams().removeFirst();
		
		_currentFunctionInstructions.add(
				new VirtualFunctionCallInstruction(
						_returnRegister, 
						objRegister, 
						_lirStruct.ClassStructures.get((node.GetInvokedExpression()).GetType().toString()).
								GetMethodIndex(node.GetFunctionName()),
								Params,
						node));
		return null;
	}
	
	protected LinkedList<Data> ConvertExpressionListToParams(LinkedList<Expression> list)
	{
		return ConvertExpressionListToParams(list, false);
	}
	
	private LinkedList<Data> ConvertExpressionListToParams(LinkedList<Expression> params, boolean isVirtualCall)
	{
		DataWithIndex[] retArray = new DataWithIndex[params.size()];
		
		ReturnAsWhat lastRetAsWhat = _returnAsWhat;
		_returnAsWhat = ReturnAsWhat.SimpleMemory;
		
		ExpressionWithIndex[] paramsArr = new ExpressionWithIndex[params.size()];
		int index = 0;
		for(Expression exp : params)
		{
			paramsArr[index] = new ExpressionWithIndex(exp, index);
			index++;
		}; 
			
		Arrays.sort(paramsArr,  new java.util.Comparator<ExpressionWithIndex>()  
				{
					/**
					 * sorting from the biggest to the smallest
					 */
					public int compare(ExpressionWithIndex o1, ExpressionWithIndex o2) 
					{
						return o2.Exp.GetNumOfRegisters() - o1.Exp.GetNumOfRegisters(); 
					}
				});
		
		int tempRegisterCounter = _registerCounter;
		index = 0;
		for (ExpressionWithIndex param : paramsArr)
		{
			_registerCounter = tempRegisterCounter;
			if (isVirtualCall && param.Index == 0)
			{
				_returnAsWhat = ReturnAsWhat.Register;
				retArray[index++] = new DataWithIndex((Data)param.Exp.Visit(this), param.Index);
				_returnAsWhat = ReturnAsWhat.SimpleMemory;
			}
			else
				retArray[index++] = new DataWithIndex(InsureNotNegativeImmediate((Data)param.Exp.Visit(this)), param.Index);
			
			tempRegisterCounter++;
		}
		_registerCounter = tempRegisterCounter;
		
		Arrays.sort(retArray,  new java.util.Comparator<DataWithIndex>()  
				{
					/**
					 * sorting from back by index
					 */
					public int compare(DataWithIndex o1, DataWithIndex o2) 
					{
						return o1.Index - o2.Index; 
					}
				});

		LinkedList<Data> ret = new LinkedList<Data>();
		for (DataWithIndex param : retArray)
			ret.addLast(param.Exp);
		
		_returnAsWhat = lastRetAsWhat;
		return ret;
	}
	
	private class ExpressionWithIndex
	{
		public Expression Exp;
		public int Index;
		public boolean IsObj = false;
		
		public ExpressionWithIndex(Expression exp, int index) { Exp = exp; Index = index; }
		public ExpressionWithIndex(Expression exp, int index, boolean isObj) { this(exp, index); IsObj = isObj; }
	}
	
	private class DataWithIndex
	{
		public Data Exp;
		public int Index;
		
		public DataWithIndex(Data exp, int index) { Exp = exp; Index = index; }
	}
	
	public Object Visit(ArrayLocation node)
	{
		ReturnAsWhat returnAsWhat = _returnAsWhat;
		
		Register returnRegister = new Register(_registerCounter);
		Data index;
		
		if (node.GetArray().GetNumOfRegisters() >= node.GetIndex().GetNumOfRegisters())
		{
			_returnAsWhat = ReturnAsWhat.Register;
			node.GetArray().Visit(this);
			_registerCounter = returnRegister.GetIndex() + 1;
			
			_returnAsWhat = ReturnAsWhat.SimpleMemory;
			index = (Data)node.GetIndex().Visit(this);
			_returnAsWhat = returnAsWhat;
		}
		else
		{
			_returnAsWhat = ReturnAsWhat.SimpleMemory;
			index = (Data)node.GetIndex().Visit(this);
			_registerCounter = returnRegister.GetIndex() + (index instanceof Register ? 1 : 0);
			
			_returnAsWhat = ReturnAsWhat.Register;
			node.GetArray().Visit(this);
			_registerCounter = returnRegister.GetIndex() + 1;
			
			if (index instanceof Register)
				GenerateRegistersSwap(
						new Register(_registerCounter), new Register(_registerCounter + 1), new Register(_registerCounter + 2));
		}
		
		_returnAsWhat = returnAsWhat;
		return ReturnAsMemoryOrRegister(
				index instanceof VaribleMemory ? 
						new ArrayMemory(returnRegister, (VaribleMemory)index)
					:
						new ArrayMemory(returnRegister, (Storable)index));
	}
	
	private void GenerateRegistersSwap(Register first, Register second, Register empty)
	{
		_currentFunctionInstructions.add(new MoveInstruction(empty, first));
		_currentFunctionInstructions.add(new MoveInstruction(first, second));
		_currentFunctionInstructions.add(new MoveInstruction(second, empty));
	}
}
